using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;

namespace MadLibrary.IO
{
    /// <summary>Stream extensions.</summary>
    public static class StreamExtensions
    {
        /// <summary>
        /// Returns the compression stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="mode">The mode.</param>
        private static Stream WithCompression(this Stream stream, StreamCompressionMode mode = StreamCompressionMode.Deflate)
        {
            if(mode == StreamCompressionMode.Deflate)
                return new DeflateStream(stream, CompressionMode.Compress, true);
            else if(mode == StreamCompressionMode.GZip)
                return new GZipStream(stream, CompressionMode.Compress, true);
            else
                return stream;
        }

        /// <summary>
        /// Returns the decompression stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="mode">The mode.</param>
        private static Stream WithDecompression(this Stream stream, StreamCompressionMode mode = StreamCompressionMode.GZip)
        {
            if(mode == StreamCompressionMode.Deflate)
                return new DeflateStream(stream, CompressionMode.Decompress, true);
            else if(mode == StreamCompressionMode.GZip)
                return new GZipStream(stream, CompressionMode.Decompress, true);
            else
                return stream;
        }

        /// <summary>A Stream extension method that compress.</summary>
        /// <param name="stream">The stream to act on.</param>
        /// <param name="mode">(optional) the mode.</param>
        /// <returns>.</returns>
        public static MemoryStream Compress(this Stream stream, StreamCompressionMode mode = StreamCompressionMode.GZip)
        {
            MemoryStream ms = new MemoryStream();
            using(Stream cs = ms.WithCompression(mode)) stream.CopyTo(cs);
            stream.Dispose();
            return ms.ToArray().ToMemoryStream();
        }

        /// <summary>A Stream extension method that de compress.</summary>
        /// <param name="stream">The stream to act on.</param>
        /// <param name="mode">(optional) the mode.</param>
        /// <returns>.</returns>
        public static MemoryStream Decompress(this Stream stream, StreamCompressionMode mode = StreamCompressionMode.GZip)
        {
            MemoryStream ms = new MemoryStream();
            using(Stream cs = stream.WithDecompression(mode)) cs.CopyTo(ms);
            stream.Dispose();
            return ms.ToArray().ToMemoryStream();
        }

        /// <summary>A string extension method that converts this object to a stream.</summary>
        /// <param name="data">The data to act on.</param>
        /// <param name="encoding">(optional, default is UTF-8) the encoding.</param>
        /// <returns>The given data converted to a Stream.</returns>
        public static MemoryStream ToMemoryStream(this string data, Encoding encoding = null)
        {
            return new MemoryStream((encoding ?? Encoding.UTF8).GetBytes(data));
        }

        /// <summary>A string extension method that converts this object to a stream.</summary>
        /// <param name="buffer">The data to act on.</param>
        /// <returns>The given data converted to a Stream.</returns>
        public static MemoryStream ToMemoryStream(this byte[] buffer)
        {
            return new MemoryStream(buffer, true);
        }

        /// <summary>A Stream extension method that copies to memory described by stream.</summary>
        /// <param name="stream">The stream to act on.</param>
        /// <returns>.</returns>
        public static MemoryStream ToMemoryStream(this Stream stream)
        {
            var memoryStream = new MemoryStream((int)stream.Length);
            stream.CopyTo(memoryStream);
            return memoryStream;
        }

        /// <summary>A Stream extension method that converts this object to a file.</summary>
        /// <param name="stream">The stream to act on.</param>
        /// <param name="path">Full pathname of the file.</param>
        /// <param name="mode">(optional) the mode.</param>
        /// <returns>The given stream for fluent like syntax.</returns>
        public static Stream ToFile(this Stream stream, string path, StreamCompressionMode mode = StreamCompressionMode.None)
        {
            if(!stream.CanRead) throw new AccessViolationException("Cannot read from protected stream.");
            using(Stream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite).WithCompression(mode))
                stream.CopyTo(fs);
            stream.Close();
            return stream;
        }

        /// <summary>A Stream extension method that reads to stream from file.</summary>
        /// <param name="stream">The stream to act on.</param>
        /// <param name="path">Full pathname of the file.</param>
        /// <param name="mode">(optional) the mode.</param>
        /// <returns>.</returns>
        public static Stream FromFile(this Stream stream, string path, StreamCompressionMode mode = StreamCompressionMode.None)
        {
            if(!stream.CanWrite) throw new AccessViolationException("Cannot write to protected stream.");
            using(Stream fs = new FileStream(path, FileMode.Open, FileAccess.Read).WithDecompression(mode))
                fs.CopyTo(stream);
            return stream;
        }

        /// <summary>
        /// Reads all text.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns></returns>
        public static string ReadAllText(this Stream stream)
        {
            StringBuilder sb = new StringBuilder();
            int ch;
            while((ch = stream.ReadByte()) != -1)
            {
                sb.Append(Convert.ToChar(ch));
            }

            return sb.ToString();
        }
    }
}